package com.loadburn.heron.utils;

import com.google.common.collect.Maps;
import com.loadburn.heron.config.ResourcesLoader;
import com.loadburn.heron.exceptions.ScannerException;
import com.loadburn.heron.excetions.TemplateLoaderException;
import com.loadburn.heron.template.Template;
import com.loadburn.heron.template.TemplateSourceImpl;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletContext;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.Serializable;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Enumeration;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author slacrey (scstlinfeng@yahoo.com)
 *         Date: 13-12-27
 */
public class TemplateUtils implements Serializable {

    private static Logger logger = LoggerFactory.getLogger(TemplateUtils.class);
    private static final long serialVersionUID = -4786309002373995519L;
    private final String suffix;
    private ServletContext context;
    private static final String URL_PROTOCOL_JAR = "jar";
    private static final String URL_PROTOCOL_FILE = "file";
    private boolean recursive = true;

    public TemplateUtils(ServletContext context, String suffix) {
        this.context = context;
        this.suffix = suffix;
    }

    public TemplateUtils(String suffix) {
        this.suffix = suffix;
    }

    public Template inOneOfFullPath(String templateDirectory, String templateName) {

        Template template = null;
        if (templateDirectory.startsWith(URL_PROTOCOL_JAR)) {

            template = inClassPathOne(templateDirectory.substring(URL_PROTOCOL_JAR.length() + 2), templateName);
        } else if (templateDirectory.startsWith(URL_PROTOCOL_FILE)) {

            templateDirectory = templateDirectory.substring(URL_PROTOCOL_FILE.length()+2);
            template = inWebPathOne(FilenameUtils.normalizeNoEndSeparator(templateDirectory, true), templateName);
        }

        return template;
    }

    public Template inOne(String templateDirectory, String templateName, boolean recursive) {

        this.recursive = recursive;
        Template template = null;
        if (templateDirectory.startsWith(Template.CLASS_PATH_PREFIX)) {

            template = inClassPathOne(templateDirectory.substring(Template.CLASS_PATH_PREFIX.length()), templateName);
        } else if (templateDirectory.startsWith(Template.WEB_PATH_PREFIX)) {

            if (context == null) {
                throw new TemplateLoaderException("ServletContext为空 请使用ServletContext初始化"+this.getClass().getName());
            }
            templateDirectory = templateDirectory.substring(Template.WEB_PATH_PREFIX.length());
            String fullTemplateDirectory = context.getRealPath(templateDirectory);
            template = inWebPathOne(FilenameUtils.normalizeNoEndSeparator(fullTemplateDirectory, true), templateName);
        }

        return template;
    }

    public Map<String, Template> in(String templateDirectory, boolean recursive) {

        this.recursive = recursive;
        Map<String, Template> templateMap = null;
        if (templateDirectory.startsWith(Template.CLASS_PATH_PREFIX)) {

            templateMap = inClassPath(templateDirectory.substring(Template.CLASS_PATH_PREFIX.length()));
        } else if (templateDirectory.startsWith(Template.WEB_PATH_PREFIX)) {

            templateDirectory = templateDirectory.substring(Template.WEB_PATH_PREFIX.length());
            String fullTemplateDirectory = context.getRealPath(templateDirectory);
            templateMap = inWebPath(FilenameUtils.normalizeNoEndSeparator(fullTemplateDirectory, true));
        }

        return templateMap;
    }

    public Template inWebPathOne(String fullTemplateDirectory, String templateName) {

        String fullUrlPath = URL_PROTOCOL_FILE
                + ":" + IOUtils.DIR_SEPARATOR_UNIX + fullTemplateDirectory;
        return initTemplate(fullUrlPath, templateName);
    }

    public Map<String, Template> inWebPath(String fullTemplateDirectory) {

        Map<String, Template> templateMap = Maps.newHashMap();
        String fullUrlPath = URL_PROTOCOL_FILE
                + ":" + IOUtils.DIR_SEPARATOR_UNIX + fullTemplateDirectory;
        findTemplatesInDirPackage(fullUrlPath, fullTemplateDirectory, fullTemplateDirectory, recursive, templateMap);

        return templateMap;
    }

    public Template inClassPathOne(String fullTemplateDirectory, String templateName) {

        String fullUrlPath = URL_PROTOCOL_JAR
                + ":" + IOUtils.DIR_SEPARATOR_UNIX + fullTemplateDirectory;
        return initTemplate(fullUrlPath, templateName);
    }

    public Map<String, Template> inClassPath(String templateDirectory) {

        Map<String, Template> templateMap = Maps.newHashMap();
        String templateName = null;
        String templatePackage = null;

        Enumeration<URL> dirs;
        try {
            dirs = Thread.currentThread().getContextClassLoader().getResources(templateDirectory);
        } catch (IOException e) {
            throw new ScannerException("包不可读: " + templateDirectory, e);
        }


        while (dirs.hasMoreElements()) {
            URL url = dirs.nextElement();

            String protocol = url.getProtocol();
            String urlPath = FilenameUtils.normalizeNoEndSeparator(url.getPath(), true);
            String urlFullPath = protocol + ":" + urlPath;

            if (URL_PROTOCOL_FILE.equals(protocol)) {

                String fullPath = urlPath;
                if (fullPath.charAt(0) == IOUtils.DIR_SEPARATOR_UNIX) {
                    fullPath = fullPath.substring(1);
                }
                findTemplatesInDirPackage(urlFullPath, fullPath, urlPath, recursive, templateMap);

            } else if (URL_PROTOCOL_JAR.equals(protocol)) {

                JarFile jar;
                try {
                    jar = ((JarURLConnection) url.openConnection()).getJarFile();
                } catch (IOException e) {
                    throw new ScannerException("URL 不可读: " + url, e);
                }

                Enumeration<JarEntry> entries = jar.entries();
                while (entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement();

                    String name = FilenameUtils.separatorsToUnix(entry.getName());
                    if (name.charAt(0) == IOUtils.DIR_SEPARATOR_UNIX) {
                        name = name.substring(1);
                    }

                    if (name.startsWith(templateDirectory)) {
                        templateName = name.substring(templateDirectory.length());

                        if (recursive) {

                            if (name.endsWith(suffix) && !entry.isDirectory()) {
                                templatePackage = protocol + ":" + urlPath;
                                addTemplateToMap(templatePackage, templateName, templateMap);
                            }
                        }
                    }
                }
            }
        }

        return templateMap;
    }

    private void findTemplatesInDirPackage(String urlFullPath, String fullPath, String templateDir,
                                           final boolean recursive,
                                           Map<String, Template> templateMap) {

        File dir = new File(templateDir);

        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }

        File[] dirFiles = dir.listFiles(new FileFilter() {
            public boolean accept(File file) {
                return (recursive && file.isDirectory()) || (file.getName().endsWith(suffix));
            }
        });

        for (File file : dirFiles) {
            if (file.isDirectory()) {

                if (recursive) {
                    findTemplatesInDirPackage(urlFullPath, fullPath,
                            FilenameUtils.normalizeNoEndSeparator(file.getAbsolutePath(), true),
                            recursive,
                            templateMap);
                }

            } else {

                addTemplateToMapHandler(urlFullPath, fullPath, templateMap, file);

            }
        }

    }

    private void addTemplateToMapHandler(String urlFullPath, String fullPath, Map<String, Template> templateMap, File dir) {
        String fileUnix = FilenameUtils.separatorsToUnix(dir.getAbsolutePath());
        if (fileUnix.startsWith(fullPath)) {

            String templateName = fileUnix.substring(fullPath.length());
            addTemplateToMap(urlFullPath, templateName, templateMap);
        }
    }

    /**
     * 添加类到Map集合
     *
     * @param templatePackage 模板目录
     * @param templateName    模板名称
     * @param templateMap     模板Map
     */
    private void addTemplateToMap(String templatePackage, String templateName, Map<String, Template> templateMap) {

        Template template = initTemplate(templatePackage, templateName);
        if (template != null) {
            templateMap.put(templateName, template);
        }

    }

    private Template initTemplate(String templatePackage, String templateName) {
        URL url = null;
        String text = null;
        try {
            url = new URL(templatePackage + templateName);
            if (URL_PROTOCOL_FILE.equals(url.getProtocol())) {

                URLConnection connection = url.openConnection();
                connection.connect();
                text = ResourcesLoader.read(connection.getInputStream());

            } else if (URL_PROTOCOL_JAR.equals(url.getProtocol())) {

                JarURLConnection conn = (JarURLConnection) url.openConnection();
                text = ResourcesLoader.read(conn.getInputStream());

            }
        } catch (MalformedURLException e) {
            logger.debug(templateName + "URL异常", e);
        } catch (IOException e) {
            logger.debug(templateName+"打开失败", e);
        }
        if (text != null) {
            return new Template(templateName, text, new TemplateSourceImpl(templatePackage, url));
        }
        return null;
    }

}
